/**
 * 实现 call， apply， bind, forEach, map, filter, some, every, reduce
 */

interface Function {
  myCall: Function,
  myApply: Function,
  myBind: Function,
  myBindNew: Function
}


/**
 * call 实现
 */
Function.prototype.myCall = function(ctx:any){
 const context = ctx || window;
 const fn = Symbol('tampfn');
 const args = Array.from(arguments).slice(1);
 context[fn] = this;
 context[fn](...args);
 delete context[fn];
}

/**
 * apply 实现
 */
Function.prototype.myApply = function(ctx:any) {
  const context = ctx || window;
  const fn = Symbol('tampfn');
  const args = arguments[1];
  if(!Array.isArray(args)){
    throw new TypeError('type of params is error!');
  }
  context[fn] = this;
  context[fn](...args);
  delete context[fn];
}

/**
 * bind 实现
 */
Function.prototype.myBind = function():Function {
  const fn = this;
  const context = arguments[0];
  const args = Array.from(arguments).slice(1);
  if(typeof fn !== 'function') {
    throw new TypeError('绑定的必须是一个函数');
  }
  return function() {
    args.push.apply(args, Array.from(arguments));
    return fn.apply(context, args);
  }
}

/**
 * 可以使用new 关键词的bing方法实现
 */

Function.prototype.myBindNew = function():Function {
  const fn  = this; // this为要绑定的函数
  const context = arguments[0]; // 要绑定的上下文
  const args = Array.from(arguments).slice(1); // 绑定时传进来的参数
  const fNOP = function() {}; 
  const fBound = function(this: any):any{
    args.push.apply(args, Array.from(arguments));
    return fn.apply(this instanceof fNOP ? this : context, args) // 当使用new关键字时，this指向的是当前函数的实例
  }
  if(typeof fn !== 'function') {
    throw new TypeError('绑定的必须是一个函数');
  }
  fNOP.prototype = this.prototype; // 把绑定函数的原型指向当前要绑定的函数的原型
  fBound.prototype = new (fNOP as any)();
  return fBound;
}


const obj = {
  name: 'ww'
}

function test(this: any, a:number, b:number, c:number) {
  console.log(a + b + c);
  console.log(this.name);
}



test.myCall(obj, 1,2,3);
test.myApply(obj, [1,2,3]);
let t = test.myBind(obj, 1);
t(2,3);


/**
 * 数组相关源码实现
 */


interface Array<T> {
  myForEach: Function,
  myMap: Function,
  myFilter: Function,
  mySome: Function,
  myEvery: Function,
  myReduce: Function,
}

/**
 * forEach 实现
 */

Array.prototype.myForEach = function(cb:Function):void {
  const context = Array.from(arguments)[1] || window;
  const _arr:Array<any> = this;
  for (let index = 0; index < _arr.length; index++) {
    cb.apply(context, [_arr[index], index, _arr]);
  }
}

/**
 * map 实现
 */
Array.prototype.myMap = function(cb:Function):Array<any> {
  const _arr = this;
  if(arr==null){
    throw new Error('this is null or not defined')
  }
  const context = Array.from(arguments)[1];
  const result = [];
  let i = 0;
  while(i < _arr.length){
    result.push(cb.apply(context, [_arr[i], i, _arr]));
    i++;
  }
  return result;
}
/**
 * filter 实现
 */
Array.prototype.myFilter = function(cb:Function):Array<any> {
  const _arr = this;
  const context = Array.from(arguments)[1];
  const result = [];
  let i = 0;
  function isTrue(index: number) {
    return cb.apply(context, [_arr[index], index, _arr]);
  };
  while(i< _arr.length) {
    if(isTrue(i)){
      result.push(_arr[i]);
    }
    i++;
  }
  return result;
}
/**
 * some 实现
 */
Array.prototype.mySome = function(cb:Function):Boolean{
  const _arr = this;
  if(_arr == null){
    throw new Error('this is null or not defined');
  }
  if(_arr.length===0){
    return false;
  }
  const context = Array.from(arguments)[1] || window;
  let result = false;
  let i = 0;
  function isTrue(index: number) {
    return cb.apply(context, [_arr[index], index, _arr]);
  };
  while(i<_arr.length){
    if(isTrue(i)){
      result = true;
      break;
    }
    i++;
  }
  return result;
}
/**
 * every 实现
 */
Array.prototype.myEvery = function(cb:Function):Boolean {
  const _arr = this;
  let result = true;
  let i = 0;
  const context = Array.from(arguments)[1] || window;
  if(_arr == null){
    throw new Error('this is null or not defined');
  }
  if(_arr.length<=0){
    return result;
  }

  function isTrue(index: number) {
    return cb.apply(context, [_arr[index], index, _arr]);
  };

  while(i < _arr.length){
    if(!isTrue(i)){
      result = false;
    }
    i++;
  }

  return result;
}

/**
 * reduce 实现
 */
Array.prototype.myReduce = function(cb:Function):any{
  const _arr = this;
  if(_arr == null){
    throw new Error('this is null or not defined');
  }
  let accumulator = Array.from(arguments)[1];
  let i = 0;
  if(!accumulator){
    i = 1;
    accumulator = _arr[0];
  }
  while(i<_arr.length){
    accumulator = cb.apply(null, [accumulator, _arr[i], i, _arr]);
    i++;
  }
  return accumulator;
}


let arr = [1,2,3,4,5,6];
// arr.forEach(function(this: any, item, index, arr){
//   console.log(this);
//   console.log(item, index, arr)
// }, obj)
// console.log('=================================')
// arr.myForEach(function(this: any, item: any, index: any, arr: any){
//   console.log(this);
//   console.log(item, index, arr)
// }, obj)

// const testArr = arr.map(function(this: any, item, index, arr){
//   console.log(item, index, arr)
//   return item +1;
// });

// const testArr2 = arr.myMap(function(this: any, item: number, index: any, arr: any){
//   console.log(item, index, arr)
//   return item +1;
// });
// console.log(testArr);
// console.log('======================');
// console.log(testArr2);

const testArr = [
  {
    name: 'a',
    value: 1,
  },
  {
    name: 'b',
    value: 2,
  },
  {
    name: 'c',
    value: 3,
  }
]

// const filterArr = testArr.filter(item=>item.value>2);
// const filterArr2 = testArr.filter(item=>item.value>2);
// console.log(filterArr);
// console.log('===================');
// console.log(filterArr2);

// const res = testArr.some(item=>item.value>10);
// const res2 = testArr.mySome((item: { value: number; })=>item.value>10);
// console.log(res)
// console.log(res2);

// const Everyres = testArr.every(item=>item.value<10);
// const Everyres2 = testArr.myEvery((item: { value: number; })=>item.value<10);
// console.log(Everyres)
// console.log(Everyres2);


const reduceRes = arr.reduce((acc, cur)=>{
  return acc + cur;
})

const reduceRes2 = arr.myReduce((acc: any, cur: any)=>{
  return acc + cur;
})

console.log(reduceRes);
console.log(reduceRes2);


